#include <QtGui>

#include "common.h"
#include "blockfilewriter.h"

BlockFileWriter::BlockFileWriter()
{
  m_dumpSlice = 0;
  m_dumpBlockSlab = 0;
  m_slice = 0;
  m_sliceAcc = 0;
  m_minmaxvals = 0;

  m_maxFileSize = 1024*1024*1024;
  m_blockSize = 28;
  m_overlap = 4;

  m_currLevel = 0;
  m_minLevel = 4;

  m_hdf5file = 0;

  reset();
}

BlockFileWriter::~BlockFileWriter() { reset(); }

void
BlockFileWriter::reset()
{
  if (m_hdf5file)
    m_hdf5file->close();

  m_baseFilename.clear();
  m_header = 0;
  m_depth = m_width = m_height = 0;
  m_voxelType = _UChar;
  m_bytesPerVoxel = 1;

  m_totBlocks = 0;
  m_dblocks = m_wblocks = m_hblocks = 0;

  m_dumpSlice = 0;
  m_dumpBlockSlab = 0;
  if (m_slice)
    delete [] m_slice;
  m_slice = 0;

  if (m_sliceAcc)
    delete [] m_sliceAcc;
  m_sliceAcc = 0;

  if (m_minmaxvals)
    delete [] m_minmaxvals;
  m_minmaxvals = 0;
}

QString BlockFileWriter::baseFilename() { return m_baseFilename; }
int BlockFileWriter::totalBlocks() { return m_totBlocks; }
qint64 BlockFileWriter::maxFileSize() { return m_maxFileSize; }
int BlockFileWriter::blockSize() { return m_blockSize; }
int BlockFileWriter::depth() { return m_depth; }
int BlockFileWriter::width() { return m_width; }
int BlockFileWriter::height() { return m_height; }
int BlockFileWriter::dblocks() { return m_dblocks; }
int BlockFileWriter::wblocks() { return m_wblocks; }
int BlockFileWriter::hblocks() { return m_hblocks; }


void BlockFileWriter::setBaseFilename(QString bfn) { m_baseFilename = bfn; }
void BlockFileWriter::setDepth(int d) { m_depth = d; }
void BlockFileWriter::setWidth(int w) { m_width = w; }
void BlockFileWriter::setHeight(int h) { m_height = h; }
void BlockFileWriter::setHeaderSize(int hs) { m_header = hs; }
void BlockFileWriter::setMaxFileSize(qint64 bpf) { m_maxFileSize = bpf; }
void BlockFileWriter::setBlockSize(int bs) { m_blockSize = bs-m_overlap; }
void BlockFileWriter::setVoxelType(int vt)
{
  m_voxelType = vt;
  m_bytesPerVoxel = 1;
  if (m_voxelType == _UChar) m_bytesPerVoxel = 1;
  else if (m_voxelType == _Char) m_bytesPerVoxel = 1;
  else if (m_voxelType == _UShort) m_bytesPerVoxel = 2;
  else if (m_voxelType == _Short) m_bytesPerVoxel = 2;
  else if (m_voxelType == _Int) m_bytesPerVoxel = 4;
  else if (m_voxelType == _Float) m_bytesPerVoxel = 4;
  else if (m_voxelType == _Rgb) m_bytesPerVoxel = 3;
  else if (m_voxelType == _Rgba) m_bytesPerVoxel = 4;
}

QString BlockFileWriter::fileName()
{
  QString flnm = m_baseFilename;
  flnm.chop(3);
  flnm += "h5";
  return flnm;
}

void
BlockFileWriter::createFile(bool writeHeader)
{
  QString filename = m_baseFilename;
  filename.chop(3);
  filename += "h5";

  m_hdf5file = new H5File(filename.toAscii().data(),
			  H5F_ACC_TRUNC);

  if (m_voxelType == _UChar) m_dataType.copy(DataType(PredType::NATIVE_UCHAR));
  else if (m_voxelType == _Char) m_dataType.copy(DataType(PredType::NATIVE_CHAR));
  else if (m_voxelType == _UShort) m_dataType.copy(DataType(PredType::NATIVE_USHORT));
  else if (m_voxelType == _Short) m_dataType.copy(DataType(PredType::NATIVE_SHORT));
  else if (m_voxelType == _Int) m_dataType.copy(DataType(PredType::NATIVE_INT));
  else if (m_voxelType == _Float) m_dataType.copy(DataType(PredType::NATIVE_FLOAT));
  else if (m_voxelType == _Rgb || m_voxelType == _Rgba)
    {
      size_t nRgb = 3;
      if (m_voxelType == _Rgba) nRgb = 4;

      typedef struct rgbstruct
      {
	uchar r, g, b;
      } rgb;
  
      //CompType rgbType(sizeof(rgbstruct));
      CompType rgbType(nRgb);
      rgbType.insertMember("r", 0, PredType::NATIVE_UCHAR);
      rgbType.insertMember("g", 1, PredType::NATIVE_UCHAR);
      rgbType.insertMember("b", 2, PredType::NATIVE_UCHAR);
      if (nRgb == 4)
	rgbType.insertMember("a", 3, PredType::NATIVE_UCHAR);
      m_dataType.copy(DataType(rgbType));
    }

  createDatasetStructures();
}

void
BlockFileWriter::startSliceBlock()
{
  int bb = m_blockSize+m_overlap;
  int bpb = bb*bb*bb*m_bytesPerVoxel;

  //---------------------------------
  if (m_slice)
    delete [] m_slice;

  m_slice = new uchar[m_wblocks*m_hblocks*bpb];
  memset(m_slice, 0, m_wblocks*m_hblocks*bpb);
  //---------------------------------

  //---------------------------------
  if (m_sliceAcc)
    delete [] m_sliceAcc;

  m_sliceAcc = new uchar[bb*m_width*m_height*m_bytesPerVoxel];
  memset(m_sliceAcc, 0, bb*m_width*m_height*m_bytesPerVoxel);
  //---------------------------------


  //---------------------------------
  if (m_minmaxvals)
    delete [] m_minmaxvals;

  m_minmaxvals = new uchar[2*m_dblocks*m_wblocks*m_hblocks];
  memset(m_minmaxvals, 0, 2*m_dblocks*m_wblocks*m_hblocks);

  int midx = 0;
  for(int d=0; d<m_dblocks; d++)
    for(int w=0; w<m_wblocks; w++)
      for(int h=0; h<m_hblocks; h++)
	{
	  m_minmaxvals[2*midx + 0] = 255;
	  m_minmaxvals[2*midx + 1] = 0;
	  midx++;
	}
  //---------------------------------

}

void
BlockFileWriter::endSliceBlock()
{
  if (m_slice)
    delete [] m_slice;
  m_slice = 0;

  if (m_sliceAcc)
    delete [] m_sliceAcc;
  m_sliceAcc = 0;
}

void
BlockFileWriter::setSubsamplingLevel(int s,
				     int d, int w, int h)
{
//  QMessageBox::information(0, "", QString("%1 %2 %3 %4").\
//			   arg(s).arg(d).arg(w).arg(h));

  if (s > m_minLevel)
    {
      QMessageBox::information(0, "", QString("Min Lod less than current lod : %1 %2 %3 %4").	\
			       arg(s).arg(d).arg(w).arg(h));
    }


  m_currLevel = s;

  m_depth = d;
  m_width = w;
  m_height = h;

  m_dblocks = ((m_depth/m_blockSize) + (m_depth%m_blockSize > 0));
  m_wblocks = ((m_width/m_blockSize) + (m_width%m_blockSize > 0));
  m_hblocks = ((m_height/m_blockSize)+ (m_height%m_blockSize > 0));
  m_dblocks = qMax(1, m_dblocks);
  m_wblocks = qMax(1, m_wblocks);
  m_hblocks = qMax(1, m_hblocks);

  m_totBlocks = m_dblocks * m_wblocks * m_hblocks;

  qint64 vsize = m_depth;
  vsize *= m_width;
  vsize *= m_height;


  int maxvsize = 128*1024*1024;
  if (m_voxelType == _Rgb || m_voxelType == _Rgba)
    maxvsize = 64*1024*1024;
}

void
BlockFileWriter::createDatasetStructures()
{
  hsize_t dimsf[3];
  QString dataname;
  hsize_t bdim[3];
  for(int slevel = 0; slevel<5; slevel++)
    {
      int sl = powf(2, slevel);
      int depth = m_depth/sl;
      int width = m_width/sl;
      int height = m_height/sl;

      if (depth <=0 || width <= 0 || height <= 0)
	break;

      m_minLevel = sl;

      int dblocks = ((depth/m_blockSize) + (depth%m_blockSize > 0));
      int wblocks = ((width/m_blockSize) + (width%m_blockSize > 0));
      int hblocks = ((height/m_blockSize)+ (height%m_blockSize > 0));
      dblocks = qMax(1, dblocks);
      wblocks = qMax(1, wblocks);
      hblocks = qMax(1, hblocks);

//      QMessageBox::information(0, "", QString(" %1\n %2 %3 %4\n %5 %6 %7").\
//			       arg(slevel).arg(depth).arg(width).arg(height).\
//			       arg(dblocks).arg(wblocks).arg(hblocks));

      //------------------------
      bdim[0] = bdim[1] = bdim[2] = m_blockSize+m_overlap;      
      dimsf[0] = dblocks*bdim[0];
      dimsf[1] = wblocks*bdim[1];
      dimsf[2] = hblocks*bdim[2];
      DataSpace dataspace( 3, dimsf );
      DSetCreatPropList cparms;
      cparms.setChunk(3, bdim);
      cparms.setDeflate(6);
      //------------------------

      QString groupname = QString("/lod-%1").arg(slevel);
      Group* group = new Group(m_hdf5file->createGroup(groupname.toAscii().data()));
      
      //------------------------
      dataname = QString("/lod-%1/data").arg(slevel);
      m_hdf5dataset[slevel] = new DataSet(m_hdf5file->createDataSet(dataname.toAscii().data(),
								    m_dataType,
								    dataspace,
								    cparms));
      //------------------------


      //------------------------
      {
	dataname = QString("/lod-%1/minmax").arg(slevel);
	dimsf[0] = 2*dblocks*wblocks*hblocks;
	DataSpace dataspace(1, dimsf);
	m_minmaxdata[slevel] = new DataSet(m_hdf5file->createDataSet(dataname.toAscii().data(),
								     m_dataType,
								     dataspace));
      }
      //------------------------


      //------------------------
      dataname = QString("/lod-%1/gridsize").arg(slevel);
      dimsf[0] = 3;
      DataSpace gridspace(1, dimsf);
      Attribute attrib = m_hdf5dataset[slevel]->createAttribute(dataname.toAscii().data(),
								PredType::NATIVE_INT,
								gridspace); 
      int griddim[3];
      griddim[0] = depth;
      griddim[1] = width;
      griddim[2] = height;
      attrib.write(PredType::NATIVE_INT, griddim);
      //------------------------
    }

}

void
BlockFileWriter::startAddSlice()
{
  m_dumpSlice = 0;
  m_dumpBlockSlab = 0;
  startSliceBlock();
}

void
BlockFileWriter::endAddSlice()
{
  if (m_dumpSlice > 1)
    {
      genZeroLevelSliceBlocks();
      dumpSliceBlocks();
    }

  m_dumpSlice = 0;
  m_dumpBlockSlab = 0;

  //m_hdf5file->close();

  endSliceBlock();

  saveDict();
}

void
BlockFileWriter::saveDict()
{
  hsize_t dimsf[1]; // dataset dimensions
  dimsf[0] = 2*m_dblocks*m_wblocks*m_hblocks;
  DataSpace dataspace(1, dimsf);
  DataSpace memspace(1, dimsf);    
  m_minmaxdata[m_currLevel]->write(m_minmaxvals,
				   m_dataType,
				   memspace,
				   dataspace );
}

//void
//BlockFileWriter::setSlice(int d, uchar *tmp)
//{
//  if (m_dumpSlice == 0) // set slice 0 for first two slices
//    memcpy(m_sliceAcc,
//	   tmp,
//	   m_width*m_height*m_bytesPerVoxel);
//  
//  m_dumpSlice++;
//
//  memcpy(m_sliceAcc + m_dumpSlice*m_width*m_height*m_bytesPerVoxel,
//	 tmp,
//	 m_width*m_height*m_bytesPerVoxel);
//  
//  if (m_dumpSlice == m_blockSize+1)
//    {
//      m_dumpSlice = 1;
//      genZeroLevelSliceBlocks();
//      dumpSliceBlocks();
//      m_dumpBlockSlab++;
//
//      memcpy(m_sliceAcc,
//	     m_sliceAcc + m_blockSize*m_width*m_height*m_bytesPerVoxel,
//	     2*m_width*m_height*m_bytesPerVoxel);
//      
//      int bb = m_blockSize+m_overlap;
//      int bpb = bb*bb*bb*m_bytesPerVoxel;
//      memset(m_slice, 0, m_wblocks*m_hblocks*bpb);
//      memset(m_sliceAcc + 2*m_width*m_height*m_bytesPerVoxel,
//	     0, m_blockSize*m_width*m_height*m_bytesPerVoxel);
//    }
//}

void
BlockFileWriter::setSlice(int d, uchar *tmp)
{
  int o2 = m_overlap/2;

  if (m_dumpSlice == 0) // set slice 0 for first two slices
    {
      memcpy(m_sliceAcc,
	     tmp,
	     m_width*m_height*m_bytesPerVoxel);

      memcpy(m_sliceAcc + m_width*m_height*m_bytesPerVoxel,
	     tmp,
	     m_width*m_height*m_bytesPerVoxel);

      m_dumpSlice = 1;
    }
  
  m_dumpSlice++;

  memcpy(m_sliceAcc + m_dumpSlice*m_width*m_height*m_bytesPerVoxel,
	 tmp,
	 m_width*m_height*m_bytesPerVoxel);
  
  if (m_dumpSlice == m_blockSize+m_overlap-1)
    {
      m_dumpSlice = m_overlap-1;
      genZeroLevelSliceBlocks();
      dumpSliceBlocks();
      m_dumpBlockSlab++;

      memcpy(m_sliceAcc,
	     m_sliceAcc + m_blockSize*m_width*m_height*m_bytesPerVoxel,
	     m_overlap*m_width*m_height*m_bytesPerVoxel);
      
      int bb = m_blockSize+m_overlap;
      int bpb = bb*bb*bb*m_bytesPerVoxel;
      memset(m_slice, 0, m_wblocks*m_hblocks*bpb);
      memset(m_sliceAcc + m_overlap*m_width*m_height*m_bytesPerVoxel,
	     0, m_blockSize*m_width*m_height*m_bytesPerVoxel);
    }
}

void
BlockFileWriter::genZeroLevelSliceBlocks()
{
  int bb = m_blockSize+m_overlap;
  int bpb = bb*bb*bb*m_bytesPerVoxel;
  int bps = bb*bb*m_bytesPerVoxel;

  int o2 = m_overlap/2;

  int dno = m_dumpBlockSlab;

  for (int ldno=0; ldno<bb; ldno++)
    {
      uchar *tmp = m_sliceAcc + ldno*m_width*m_height*m_bytesPerVoxel;
      int lbno = 0;
      for(int w=0; w<m_wblocks; w++)
	for(int h=0; h<m_hblocks; h++)
	  {
	    int lbidx = lbno*bpb + ldno*bps;
	    lbno++;

	    if (m_bytesPerVoxel == 1)
	      {
		// calculate minmax values for level-0
		uchar minval = 255;
		uchar maxval = 0;
		int idx = 0;
		for(int iw=-o2; iw<m_blockSize+o2; iw++)
		  for(int ih=-o2; ih<m_blockSize+o2; ih++)
		    {
		      int jw = qMin(qMax(0, w*m_blockSize + iw), m_width-1);
		      int jh = qMin(qMax(0, h*m_blockSize + ih), m_height-1);
		      uchar val = tmp[jw*m_height + jh];

		      m_slice[lbidx + idx] = val;
		      minval = qMin(minval, val);
		      maxval = qMax(maxval, val);

		      idx++;
		    }
		int midx = 2*(dno*m_wblocks*m_hblocks + w*m_hblocks + h);
		m_minmaxvals[midx + 0] = qMin(minval, m_minmaxvals[midx + 0]);
		m_minmaxvals[midx + 1] = qMax(maxval, m_minmaxvals[midx + 1]);
	      }
	  }
    }
}

void
BlockFileWriter::dumpSliceBlocks()
{
  int bb = m_blockSize+m_overlap;
  int bpb = bb*bb*bb*m_bytesPerVoxel;

  int dno = m_dumpBlockSlab;
  int blkno = dno*m_wblocks*m_hblocks;	  

  int lbno = 0;
  for(int w=0; w<m_wblocks; w++)
    for(int h=0; h<m_hblocks; h++)
      {	
	hsize_t bdim[3];              // block dimensions
	bdim[0] = bb;
	bdim[1] = bdim[0];
	bdim[2] = bdim[0];

	hsize_t offset[3];              // block dimensions
	offset[0] = dno*bb;
	offset[1] = w*bb;
	offset[2] = h*bb;

	hsize_t dimsf[3];              // dataset dimensions
	dimsf[0] = m_dblocks*bb;
	dimsf[1] = m_wblocks*bb;
	dimsf[2] = m_hblocks*bb;

	DataSpace memspace(3, bdim);
	DataSpace dspace(3, dimsf);

	dspace.selectHyperslab(H5S_SELECT_SET, bdim, offset);

	m_hdf5dataset[m_currLevel]->write(m_slice + lbno*bpb,
					  m_dataType,
					  memspace, dspace );

	lbno ++;
      }
}
